/*
 * Copyright (c) 2021 Futurewei Technologies, Inc.
 *
 * clang2mpl is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan
 * PSL v2. You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the
 * Mulan PSL v2 for more details.
 */
#include "Clang2MapleResult.h"
#include "Clang2MapleOptions.h"

using namespace maple;

BaseNode *Result::getNode() { return Node; }

void Result::setNode(BaseNode *N, int64_t UID, MIRType *Ty, TypeAttrs Attrs,
                     bool MD) {
  ASSERT(kOpcodeInfo.IsStmt(N->op) || Ty,
         "Must include a type with expression result");
  Node = N;
  UniqueID = UID;
  NodeTy = Ty;
  NodeTyAttrs = Attrs;
  MayDrop = MD;
}

clang::RecordDecl *Result::setBaseRecordDecl(clang::RecordDecl *Record) {
  if (!BaseRecord) {
    BaseRecord = Record;
  }
  return BaseRecord;
}

BaseNode *Result::getAddr() {
  ASSERT(isDeref(), "called getAddr on Result that is not a deref");
  return Node;
}

MIRType *Result::getAddrTy() {
  ASSERT(isDeref(), "called getAddrTy on Result that is not a deref");
  return NodeTy;
}

TypeAttrs Result::getAddrTyAttrs() {
  ASSERT(isDeref(), "called getAddrTyAttrs on Result that is not a deref");
  return NodeTyAttrs;
}

MIRType *Result::getValueTy() {
  if (isDeref()) {
    return DerefTy;
  }
  return NodeTy;
}

TypeAttrs Result::getValueTyAttrs() {
  if (isDeref()) {
    return DerefTyAttrs;
  }
  return NodeTyAttrs;
}

void Result::setValueTy(MIRType *Ty, TypeAttrs Attrs, bool IsDeref) {
  if (isDeref()) {
    if (IsDeref) {
      DerefTy = Ty;
      DerefTyAttrs = Attrs;
    } else {
      NodeTy = Ty;
      NodeTyAttrs = Attrs;
    }
  } else {
    NodeTy = Ty;
    NodeTyAttrs = Attrs;
    if (Node) {
      Node->SetPrimType(Ty->GetPrimType());
    }
  }
}

MIRSymbol *Result::getSym() { return Sym; }

void Result::setSym(MIRSymbol *S, MIRType *Ty, bool MD) {
  Sym = S;
  NodeTy = Ty;
  MayDrop = MD;
}

bool Result::isDeref() { return DerefTy != nullptr; }
bool Result::isSym() { return Sym != nullptr; }
bool Result::mayDrop() { return MayDrop || (!getNode() && !getSym()); }

void Result::setResult(Result &Other) {
  Node = Other.Node;
  UniqueID = Other.UniqueID;
  Sym = Other.Sym;
  Field = Other.Field;
  NodeTy = Other.NodeTy;
  DerefTy = Other.DerefTy;
  Loc = Other.Loc;
  MayDrop = Other.MayDrop;
}

void Result::appendStmtBefore(StmtNode *Node) {
  // If Node is a block, append the statements in the block
  if (Node->GetOpCode() == OP_block) {
    BlockNode *Block = static_cast<BlockNode *>(Node);
    for (auto &Stmt : Block->GetStmtNodes()) {
      StmtsBefore.push_back(&Stmt);
    }
  } else {
    StmtsBefore.push_back(Node);
  }
}

void Result::prependStmtBefore(StmtNode *Node) {
  StmtsBefore.insert(StmtsBefore.begin(), Node);
}

void Result::appendStmtAfter(StmtNode *Node) {
  // If Node is a block, append the statements in the block
  if (Node->GetOpCode() == OP_block) {
    BlockNode *Block = static_cast<BlockNode *>(Node);
    for (auto &Stmt : Block->GetStmtNodes()) {
      StmtsAfter.push_back(&Stmt);
    }
  } else {
    StmtsAfter.push_back(Node);
  }
}

void Result::appendStmts(Result R) {
  StmtsBefore.insert(StmtsBefore.end(), R.beginStmtsBefore(),
                     R.endStmtsBefore());
  StmtsAfter.insert(StmtsAfter.end(), R.beginStmtsAfter(), R.endStmtsAfter());
}

static bool isAssign(Opcode Op) {
  return Op == OP_dassign || Op == OP_iassign || kOpcodeInfo.IsCallAssigned(Op);
}

bool Result::willExpand() {
  if (Node) {
    if (isDeref())
      return false;

    // If a call is used as an rvalue, we need to create a temporary to assign
    // the return value to, then read from that for the rvalue.
    Opcode Op = Node->GetOpCode();
    if (kOpcodeInfo.IsCall(Op))
      return true;
    // If the RHS of an assignment is another assignment, then we need to
    // emit that assignment statement first, then use the LHS of that
    // assignment as the operand.
    if (isAssign(Op))
      return true;
    return false;
  }
  return false;
}

bool Result::isSimple() {
  if (useImprovedIsSimple() || useSimpleShortCircuit())
    return StmtsBefore.size() == 0 && StmtsAfter.size() == 0 && (!willExpand());
  else
    return StmtsBefore.size() == 0 && StmtsAfter.size() == 0 &&
           (isSym() || Node->GetOpCode() == OP_dread);
}

static bool canSpeculateNode(BaseNode *N) {
  Opcode Op = N->GetOpCode();
  switch (Op) {
  case OP_div:
  case OP_rem:
  case OP_iread:
    return false;
  default:
    break;
  }
  if (kOpcodeInfo.NotPure(Op))
    return false;

  for (size_t i = 0; i < N->GetNumOpnds(); i++) {
    if (!canSpeculateNode(N->Opnd(i)))
      return false;
  }
  return true;
}

bool Result::canSpeculate() {
  if (isDeref())
    return false;
  if (Node) {
    return canSpeculateNode(Node);
  }
  return true;
}
